from lexer import Lexer, Token
from parser import Parser, UsingStatement, ClassDef, MethodDef, PrintStatement, Variable, Number, String, MethodCall, ObjectCreation, Boolean

class MOLightObject:
    def __init__(self, cls):
        self.cls = cls
        self.fields = {}

class MOLightInterpreter:
    def __init__(self):
        self.variables = {}
        self.functions = {}
        self.classes = {}
        self.modules = {}
        self.current_class = None
        
    def interpret(self, code):
        lexer = Lexer(code)
        parser = Parser(lexer)
        ast = parser.parse()
        
        # First pass: process all function and class definitions
        for node in ast:
            if isinstance(node, (MethodDef, ClassDef)):
                self.execute(node)
                
        # Second pass: process other statements
        for node in ast:
            if not isinstance(node, (MethodDef, ClassDef)):
                self.execute(node)
            
    def execute(self, node):
       
        if isinstance(node, UsingStatement):
            self.modules[node.module_name] = {}
            
        elif isinstance(node, ClassDef):
            self.classes[node.name] = node
            self.current_class = node.name
            
        elif isinstance(node, MethodDef):
            if self.current_class:
                if not hasattr(self.classes[self.current_class], 'methods'):
                    self.classes[self.current_class].methods = {}
                self.classes[self.current_class].methods[node.name] = node
            else:
                self.functions[node.name] = node
                
        elif isinstance(node, PrintStatement):
            value = self.evaluate(node.expr)
            print(value)
            
        elif isinstance(node, tuple) and node[0] == "assignment":
            var_name = node[1]
            value = self.evaluate(node[2])
            self.variables[var_name] = value
            
        elif isinstance(node, tuple) and node[0] == "if":
               
                condition = self.evaluate(node[1])
            
                
                if condition:
                  
                    for stmt in node[2]:
                        self.execute(stmt)
                elif len(node) > 3:  # Has else clause
                  
                    for stmt in node[3]:
                        self.execute(stmt)
                        
        elif isinstance(node, MethodCall):
            self.evaluate(node)
            
        elif isinstance(node, tuple) and node[0] == "inputon":
            prompt = self.evaluate(node[1])
            value = input(prompt)
            return value
            
        elif isinstance(node, tuple) and node[0] == "readfile":
            filename = self.evaluate(node[1])
            try:
                with open(filename, 'r') as f:
                    return f.read()
            except Exception as e:
                raise Exception(f"Failed to read file {filename}: {e}")
                
        elif isinstance(node, tuple) and node[0] == "writefile":
            filename = self.evaluate(node[1])
            content = self.evaluate(node[2])
            try:
                with open(filename, 'w') as f:
                    f.write(str(content))
            except Exception as e:
                raise Exception(f"Failed to write to file {filename}: {e}")
            
    def evaluate(self, expr):
        if isinstance(expr, Number):
            return expr.value
        elif isinstance(expr, String):
            return expr.value
        elif isinstance(expr, Boolean):
            return expr.value
        elif isinstance(expr, Variable):
            if expr.name in self.variables:
                return self.variables[expr.name]
            raise Exception(f"Undefined variable: {expr.name}")
        elif isinstance(expr, MethodCall):
            # Handle method calls
            if expr.obj_name is None:  # Global function
                if expr.method_name in self.functions:
                    return self.call_function(expr.method_name, expr.args)
            else:  # Method call on object
                if expr.obj_name in self.variables:
                    obj = self.variables[expr.obj_name]
                    if expr.method_name in obj.cls.methods:
                        return self.call_method(obj, expr.method_name, expr.args)
            raise Exception(f"Undefined function or method: {expr.method_name}")
        elif isinstance(expr, ObjectCreation):
            return self.create_object(expr.class_name)
        elif isinstance(expr, tuple):
            if expr[0] == "+":  # String concatenation
                left = self.evaluate(expr[1])
                right = self.evaluate(expr[2])
                return str(left) + str(right)
            elif expr[0] == "-":  # Subtraction
                left = self.evaluate(expr[1])
                right = self.evaluate(expr[2])
                return left - right
            elif expr[0] in ("==", "!=", "<", ">", "<=", ">="):
                left = self.evaluate(expr[1])
                right = self.evaluate(expr[2])
                
                if expr[0] == "==":
                    return left == right
                elif expr[0] == "!=":
                    return left != right
                elif expr[0] == "<":
                    return left < right
                elif expr[0] == ">":
                    return left > right
                elif expr[0] == "<=":
                    return left <= right
                elif expr[0] == ">=":
                    return left >= right
        else:
            raise Exception(f"Unknown expression type: {type(expr)}")
        
    def call_function(self, name, args):
        func = self.functions[name]
        if len(args) != len(func.params):
            raise Exception(f"Argument count mismatch for function {name}")
            
        # Evaluate arguments
        evaluated_args = [self.evaluate(arg) for arg in args]
     
        
        # Save current variables
        old_variables = self.variables.copy()
        
        # Bind parameters to local variables
        for param_name, arg_value in zip(func.params, evaluated_args):
            self.variables[param_name] = arg_value
            
        # Execute function body
        result = None
        for stmt in func.body:
        
            result = self.execute(stmt)
            
        # Restore variables
        self.variables = old_variables
        
        return result
        
    def call_method(self, obj, name, args):
        method = obj.cls.methods[name]
        if len(args) != len(method.params):
            raise Exception(f"Argument count mismatch for method {name}")
            
        # Evaluate arguments
        evaluated_args = [self.evaluate(arg) for arg in args]
        
        # Execute method body
        result = None
        for stmt in method.body:
            result = self.execute(stmt)
        return result
        
    def create_object(self, class_name):
        if class_name in self.classes:
            return MOLightObject(self.classes[class_name])
        raise Exception(f"Undefined class: {class_name}")

if __name__ == "__main__":
    import sys
    interpreter = MOLightInterpreter()
    
    if len(sys.argv) > 1:
        # Read from file
        with open(sys.argv[1], 'r') as f:
            code = f.read()
        interpreter.interpret(code)
    else:
        # REPL mode
        while True:
            try:
                code = input("molight> ")
                if code.strip() == "exit":
                    break
                interpreter.interpret(code)
            except Exception as e:
                print(f"Error: {e}")
